﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace ChartEdit
{
    public class RendererNote
    {
        public PointF XYf;
        public Point XY;
        public PointF SustainXYf;
        public Point SustainXY;
        public ChartBB BBox;
        public ChartElement CE;
        public ChartElementPart CEP;

        public Note Note;

        public RendererNote()
        {
        }
        public RendererNote(float x, float y, Note note, ChartBB bbox, ChartElement ce, ChartElementPart cep)
        {
            XYf = new PointF(x, y);
            SustainXYf = XYf;
            Note = note;
            BBox = bbox;
            CE = ce;
            CEP = cep;
        }
        public RendererNote(float x, float y, Note note, ChartBB bbox, ChartElement ce, ChartElementPart cep, float sy)
        {
            XYf = new PointF(x, y);
            SustainXYf = new PointF(x, sy);
            Note = note;
            BBox = bbox;
            CE = ce;
            CEP = cep;
        }
    }
    public class RendererNoteList : List<RendererNote>
    {
    }
    public class RendererBeatLine
    {
        public beatType BeatType;
        public float Y;

        public RendererBeatLine(beatType beatType, float y)
        {
            BeatType = beatType;
            Y = y;
        }
    }
    public class RendererSpecial : List<PointF>
    {
        public string Name;
        public int SIndex;

        public RendererSpecial(string name, int index, PointF[] point)
        {
            Name = name;
            SIndex = index;
            for (int i = 0; i < point.Length; i++)
            {
                Add(point[i]);
            }
        }
        public RendererSpecial(string name, int index, float x1, float y1, float x2, float y2)
        {
            Name = name;
            SIndex = index;
            Add(new PointF(x1, y1));
            Add(new PointF(x2, y1));
            Add(new PointF(x2, y2));
            Add(new PointF(x1, y2));
        }
    }


    public class PreRenderer
    {
        public PreRenderer(Editor ed)
        {
            this.ed = ed;
            sW = new System.Diagnostics.Stopwatch();
            OT = new OffsetTransformer(ed.chart);

            NoteLists = new List<RendererNoteList>();
            GhostNoteList = new RendererNoteList();
            BeatList = new List<RendererBeatLine>();
            SpecialList = new List<RendererSpecial>();
        }



        private float nOffset;
        public OffsetTransformer OT;

        Editor ed;
        #region editor references
        private int width { get { return ed.Width; } }
        private int height { get { return ed.Height; } }

        private float fbX { get { return ed.FbX; } }//Left of Fretboard
        private float fbXR { get { return ed.FbXR; } }//Right of Fretboard
        private float fbXB { get { return ed.FbXB; } }//Left of Fretboard (buffered)
        private float fbWidth { get { return ed.FbWidth; } }
        private float fbWidthB { get { return ed.FbWidthB; } }


        private TSManager tSM { get { return ed.tSM; } }
        private int lastEventOffset { get { return ed.lastEventOffset; } } //To make it easy to scroll to the end

        private float secPerH { get { return ed.SecPerH; } }
        private float sphScaled { get { return ed.SphScaled; } }//SecPerH*height
        private float cOffset { get { return ed.COffset; } } //CrudeOffset - place in chart - 0 is start, 1 is end//time,notoffset 

        private Chart chart { get { return ed.chart; } }
        private NoteTrack noteTrack { get { return ed.noteTrack; } }

        private ThemeManager themeManager { get { return ed.themeManager; } }
        private Theme currentTheme { get { return ed.currentTheme; } }

        #endregion

        private int realOffset;//Current offset
        private float timeOffset;//Current offset in time (seconds)
        private int realOffsetStart;// First offset to render at
        private int realOffsetEnd;// Last offset to render at
        public float YHit;

        //public float DrawOffset
        //{
        //    get { return timeOffset/sphScaled; }
        //}

        //Lag compensation (Possibly overkill but we'll see)
        private System.Diagnostics.Stopwatch sW;
        private float[] executionTimes = new float[32];
        public float RendererLag=0;
        private int eIndex = 0;
        public float Lag { get { return executionTimes.Average() + RendererLag; } }

        public List<RendererNoteList> NoteLists;
        public RendererNoteList GhostNoteList;
        public List<RendererBeatLine> BeatList;
        public List<RendererSpecial> SpecialList;

        public float DebugSnapY;

        public float Transform(int offset)
        {
            return height - ((OT.GetTime(offset) - timeOffset) / sphScaled);
        }
        public float Transform(float time)
        {
            return height - ((time - timeOffset) / sphScaled);
        }

        public void CalculateRenderingInfo(bool compensateLag)
        {
            //lock (ed)
            {
                sW.Reset();
                sW.Start();

                if (compensateLag) nOffset = cOffset + Lag*ed.song.GetSpeed();
                else nOffset = cOffset;

                realOffset = OT.GetOffset(nOffset, -secPerH / 12);//Current offset
                realOffsetStart = OT.GetOffset(nOffset, -secPerH / 2);
                timeOffset = OT.GetTime(realOffset);//Current offset time
                realOffsetEnd = OT.GetOffset(nOffset, secPerH);//Offset of Beat at the top of the screen

                YHit = Transform(nOffset);
                DebugSnapY = Transform(tSM.NearestSnapPointBeat(
                    ed.Quantization,
                    OT.GetOffset(cOffset)
                    ));

                calcNotes(currentTheme.RealRenderOrder);
                calcGhostNotes();
                calcSpecials();
                calcBeatLines();

                executionTimes[eIndex] = (float)sW.Elapsed.Ticks / TimeSpan.TicksPerSecond;
                eIndex++;
                eIndex %= executionTimes.Length;
                sW.Stop();
            }
        }

        private void calcBeatLines()
        {
            BeatList.Clear();
            int step = chart.HalfResolution;
            float Y;
            for (int i = ((realOffsetStart) - (realOffsetStart % step)); i < realOffsetEnd; i += step)
            {
                if (tSM[i] == beatType.NotABeat)
                    continue;
                Y = Transform(i);
                BeatList.Add(new RendererBeatLine(tSM[i],Y));
            }
        }

        private void calcSpecials()
        {
            SpecialList.Clear();
            if(ed.noteTrack==null)
                return;
            if (ed.noteTrack.SpecialList.Count > 0)
            {
                int indexStart = OT.prevNIndex(realOffsetStart, ed.noteTrack.SpecialList);
                int indexEnd = OT.nextNIndex(realOffsetEnd, ed.noteTrack.SpecialList);
                
                //if (indexEnd >= ed.noteTrack.SpecialList.Count())
                //    indexEnd = SpecialList.Count() - 1;
                //if (indexStart < 0)
                //    indexStart = 0;

                for (int i = indexStart; i <= indexEnd; i++)
                {
                    Note n = ed.noteTrack.SpecialList[i];
                    float y1 = Transform(n.OffsetEnd);
                    float y2 = Transform(n.Offset);
                    string name = Globals.Instance.GameProperties[0].TrackProperties[0].SpecialProperties[(int)n.SpecialFlag].Name;
                    SpecialList.Add(new RendererSpecial(name,(int)n.SpecialFlag,fbX,y1,fbXR,y2));
                }
            }
        }

        private void calcNotes(List<int> fretOrder)
        {
            NoteLists.Clear();
            if (ed.noteTrack != null)
            {
                float x, y, y2;
                //Cycle through each different fret
                for (int j = 0; j < fretOrder.Count; j++)
                {
                    NoteLists.Add(new RendererNoteList());

                    //Break out if the track is invalid
                    if (fretOrder[j] >= noteTrack.HighestFret)
                        break;
                    if (noteTrack.Count(fretOrder[j]) == 0)
                        continue;

                    int blah = noteTrack.Count(fretOrder[j]);

                    int indexStart = Math.Max(OT.nearNIndexEnd(realOffsetStart,
                        noteTrack, fretOrder[j]) - noteTrack.HighestFret, 0);
                    int indexEnd = Math.Min(OT.nearNIndex(realOffsetEnd + chart.Resolution,
                        noteTrack, fretOrder[j])+noteTrack.HighestFret,noteTrack.noteList[j].Count()-1);



                    x = fbXB + currentTheme[j].Offset.X * fbWidthB;

                    for (int i = indexEnd; i >= indexStart; i--)
                    {
                        if (noteTrack[i, fretOrder[j]].Type != NoteType.Regular)
                            continue;

                        string index = "";
                        if (noteTrack[i, fretOrder[j]].IsArmored)
                            index += "armor";
                        if (noteTrack[i, fretOrder[j]].IsHopo)
                            index += "hopo";
                        if (noteTrack[i, fretOrder[j]].IsInSlide)
                            index += "slide";
                        if (noteTrack[i, fretOrder[j]].IsInSP)
                            index += "sp";

                        y = Transform(noteTrack[i, fretOrder[j]].Offset);
                        y2 = Transform(noteTrack[i, fretOrder[j]].OffsetEnd);
                        if(y2==y)
                            NoteLists[j].Add(new RendererNote(x, y, noteTrack[i, fretOrder[j]], currentTheme[j].BoundingBoxResized, currentTheme[j], currentTheme[j][index]));
                        else
                            NoteLists[j].Add(new RendererNote(x, y, noteTrack[i, fretOrder[j]], currentTheme[j].BoundingBoxResized, currentTheme[j], currentTheme[j][index], y2));
                    }

                }
            }
        }
        private void calcGhostNotes()
        {
            GhostNoteList.Clear();
                float x, y, y2;
                List<Note> GN = ed.SE.GhostNotes;
                for (int i = GN.Count()-1; i >= 0; i--)
                {
                    x = fbXB + currentTheme[GN[i].Fret].Offset.X * fbWidthB;
                    y = Transform(GN[i].Offset);
                    y2 = Transform(GN[i].OffsetEnd);
                    if (y2 == y)
                        GhostNoteList.Add(new RendererNote(x, y, GN[i], currentTheme[GN[i].Fret].BoundingBoxResized, currentTheme[GN[i].Fret], currentTheme[GN[i].Fret][0]));
                    else
                        GhostNoteList.Add(new RendererNote(x, y, GN[i], currentTheme[GN[i].Fret].BoundingBoxResized, currentTheme[GN[i].Fret], currentTheme[GN[i].Fret][0], y2));
                }

                
            
        }
    }
}
